package org.uranus.webauth.fido.uranus;

import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.yubico.internal.util.JacksonCodecs;
import com.yubico.util.Either;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.exception.Base64UrlException;
import com.yubico.webauthn.extension.appid.InvalidAppIdException;

import demo.webauthn.WebAuthnServer;
import demo.webauthn.data.AssertionRequestWrapper;
import demo.webauthn.data.RegistrationRequest;

@RestController
@RequestMapping("/fido/api/v1/")
public class FidoTestController {
	 private static final Logger logger = LoggerFactory.getLogger(FidoTestController.class);
	@Value("${minos.fido.origin}")
	private String origin;
	@Value("${server.port}")
	private String port;
	@Value("${server.servlet.contextPath}")
	private String contextPath;
	@Autowired
	private UranusMvcConfig minosMvcConfig;
	
	private  WebAuthnServer server;
	private final ObjectMapper jsonMapper = JacksonCodecs.json();
	@PostConstruct
	void init() throws InvalidAppIdException, CertificateException{
		server = new WebAuthnServer();
	}
	
	@RequestMapping
	public Object index() {
		
		Map<String, Object> map = Maps.newHashMap();
		Map<String, String> map1 = Maps.newHashMap();
		Map<String, String> map2 = Maps.newHashMap();
		map.put("actions", map1);
		map.put("info", map2);
		
		map1.put("authenticate", origin+contextPath+"/fido/api/v1/authenticate");
		map1.put("deleteAccount", origin+contextPath+"/fido/api/v1/delete-account");
		map1.put("deregister", origin+contextPath+"/fido/api/v1/deregister");
		map1.put("register", origin+contextPath+"/fido/api/v1/register");
		map2.put("version", origin+contextPath+"/fido/api/v1/version");
		return map;
	}
	
	@RequestMapping("register")
    public Object startRegistration(
         @RequestParam("username") String username,
         @RequestParam("displayName") String displayName,
         @RequestParam("credentialNickname") String credentialNickname,
         @RequestParam(name="requireResidentKey") boolean requireResidentKey,
         @RequestParam("sessionToken") String sessionTokenBase64
    ) throws MalformedURLException, ExecutionException, JsonProcessingException {
        logger.trace("startRegistration username: {}, displayName: {}, credentialNickname: {}, requireResidentKey: {}", username, displayName, credentialNickname, requireResidentKey);
        Either<String, RegistrationRequest> result = server.startRegistration(
            username,
            Optional.of(displayName),
            Optional.ofNullable(credentialNickname),
            requireResidentKey,
            Optional.ofNullable(sessionTokenBase64).map(base64 -> {
                try {
                    return ByteArray.fromBase64Url(base64);
                } catch (Base64UrlException e) {
                    throw new RuntimeException(e);
                }
            })
        );
        return JSONObject.parse(this.writeJson(new StartRegistrationResponse(result.right().get())));
    }

    @RequestMapping("register/finish")
    public Object finishRegistration(@RequestBody Map<String, Object> param) throws JsonProcessingException {
    	String responseJson = JSONObject.toJSONString(param);
        logger.trace("finishRegistration responseJson: {}", responseJson);
        Either<List<String>, WebAuthnServer.SuccessfulRegistrationResult> result = server.finishRegistration(responseJson);
        return JSONObject.parse(this.writeJson(result.right().get()));
        
    }
    private final class StartRegistrationResponse {
        public final boolean success = true;
        public final RegistrationRequest request;
        public final StartRegistrationActions actions = new StartRegistrationActions();
        private StartRegistrationResponse(RegistrationRequest request) throws MalformedURLException {
            this.request = request;
        }
    }
    private final class StartRegistrationActions {
        public final URL finish = new URL(origin+contextPath+"/fido/api/v1/register/finish");
        public final URL finishU2f = new URL(origin+contextPath+"/fido/api/v1/register/finish-u2f");
        private StartRegistrationActions() throws MalformedURLException {
        }
    }
	
    @RequestMapping("authenticate")
    public Object startAuthentication(
    		@RequestParam("username") String username
    ) throws MalformedURLException, JsonProcessingException {
        logger.trace("startAuthentication username: {}", username);
        Either<List<String>, AssertionRequestWrapper> request = server.startAuthentication(Optional.ofNullable(username));
        
        
        return JSONObject.parse(this.writeJson(new StartAuthenticationResponse(request.right().get())));
    }
    private final class StartAuthenticationResponse {
        public final boolean success = true;
        public final AssertionRequestWrapper request;
        public final StartAuthenticationActions actions = new StartAuthenticationActions();
        private StartAuthenticationResponse(AssertionRequestWrapper request) throws MalformedURLException {
            this.request = request;
        }
    }
    private final class StartAuthenticationActions {
        public final URL finish = new URL(origin+contextPath+"/fido/api/v1/authenticate/finish");
        private StartAuthenticationActions() throws MalformedURLException {
        }
    }
    @RequestMapping("authenticate/finish")
    public Object finishAuthentication(@RequestBody Map<String, Object> param) throws JsonProcessingException {
    	String responseJson = JSONObject.toJSONString(param);
        logger.trace("finishAuthentication responseJson: {}", responseJson);

        Either<List<String>, WebAuthnServer.SuccessfulAuthenticationResult> result = server.finishAuthentication(responseJson);
        return JSONObject.parse(this.writeJson(result.right().get()));
    }
    
    
    
    private String writeJson(Object o) throws JsonProcessingException {
            return jsonMapper.writeValueAsString(o);
    }
    
	
	
}
